package com.lottery.game.runner.v2.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.lottery.game.common.utils.DateUtils;
import com.lottery.game.common.utils.SpringApplicationUtils;
import com.lottery.game.common.utils.UUIDUtils;
import com.lottery.game.core.model.ResultEntity;
import com.lottery.game.core.model.dto.game.GameTypePlayingRelationTO;
import com.lottery.game.core.modules.entity.game.GameApiLog;
import com.lottery.game.core.modules.entity.game.GameRoom;
import com.lottery.game.core.modules.service.app.BetService;
import com.lottery.game.core.modules.service.game.GameApiService;
import com.lottery.game.core.modules.service.game.GameTypePlayingRelationService;
import com.lottery.game.core.utils.BetUtils;
import com.lottery.game.core.utils.ResultCode;
import com.lottery.game.runner.v2.RoomDetail;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class RoomUtils {

	@Autowired
	private BetService betService;

	@Autowired
	private GameTypePlayingRelationService resourceClient;

	/**
	 * 是否停机维护
	 *
	 * @return
	 */
	public RoomDetail isDownTime(GameRoom roomTime, RoomDetail detail) {
		GameTypePlayingRelationTO relationTO = resourceClient.findByTicket(detail.getRelationTO().getGameTypePlayingTicket());
		detail.setRelationTO(relationTO);
		detail.setDuration(Math.toIntExact(relationTO.getDurationTime()));
		detail.setGameTime(Math.toIntExact(relationTO.getClosedTime()));
		if (roomTime == null) {
			detail.setDownTime(60);
			detail.setDown(true);
		} else {
			Date now = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
			sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
			String nowStr = sdf.format(now);
			try {
				int isDown = BetUtils.isShutDown(nowStr, detail.getRelationTO().getMaintenanceStartTime(), detail.getRelationTO().getMaintenanceEndTime());
				if (isDown != -1) {
					detail.setDownTime(isDown);
					detail.setDown(true);
				} else {
					detail.setDownTime(0);
					detail.setDown(false);
				}
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
		return detail;
	}

	public RoomDetail getExpectTime(RoomDetail detail) {
		// 默认设置API已经down 除非下面获取到下期期号和时间
		detail.setApiDown(true);

		ResultEntity re = null;
		// 循环尝试2次 获取接口期号
		for (int i = 0; i < 2; i++) {

			log.info("游戏类型【{}】", detail.getLotteryName());

			re = getIssueApi(detail.getBetType(), detail.getExpect(), detail.getDuration() + detail.getGameTime());
			if (!ResultCode.SUCCESS.equals(re.getStatusCode())) {
				log.error("游戏类型【{}】，获取异常【{}】", detail.getLotteryName(), re.getResultInfo());
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				break;
			}
		}

		Object retObj = re.getResultInfo();
		if (retObj instanceof Map) {
			@SuppressWarnings("unchecked")
			Map<String, Object> map = (Map<String, Object>) retObj;
			// openTime已改为下期时间
			Date tmpOpenTime = (Date) map.get("openTime");
			detail.setOpenTime(tmpOpenTime);
			Integer sleepTime = (Integer) map.get("sleepTime");
			// 补偿睡眠时间，当期剩余时间不满30秒，睡眠这段时间，等下期开盘。开下下期的下注
			if (sleepTime != 0) {
				try {
					log.info("游戏类型：{}，进行{}秒补充睡眠时间。。。", detail.getBetType(), sleepTime);
					Thread.sleep(sleepTime * 1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			detail.setApiDown(false);
		}
		log.info("API状态：{}", detail.isApiDown());
		return detail;
	}

    /**
		 * 原来的DataUtil.getIssueApi
		 * 获取下期期号和开奖时间
     * @param betType
     * @return
     */
    public ResultEntity getIssueApi(String betType, String expect, Integer gameTime) {
    	log.info("获取下期期号游戏类型【{}】, 当前期号【{}】, 游戏时间【{}】", betType, expect, gameTime);
			BetResp bet = getIssueBet(betType, 2);
			log.info("获取期号结果[{}]", JSON.toJSONString(bet));
			String  lastExpect = bet.getNumber();
			Date lastOpenTime = bet.getTime();

        ResultEntity re = new ResultEntity();
        re.setStatusCode(ResultCode.ERROR);
        try {
            //如果是
            if (StringUtils.isEmpty(expect)) {
								GameTypePlayingRelationTO relationTO = resourceClient.findByCode(betType);
                //记录日志
								GameApiLog apiLog = getApiLogByJson(bet);
								apiLog.setType(String.valueOf(relationTO.getGameTypePlayingTicket()));
								apiLog.setStatus("0");
								betService.insertApiLog(apiLog);
                //获取最新一期期号，
                Map<String,Object> map = new HashMap<String, Object>();
                map.put("expect", lastExpect);
                map.put("sleepTime", 0);
                map.put("openTime", lastOpenTime);
                re.setStatusCode(ResultCode.SUCCESS);
                re.setResultInfo(map);
            } else {
                //获取expect 对应的开奖时间
                ResultEntity getOpenTimeResult = getNextExpectOpenDate(betType, expect, gameTime);
                if (!ResultCode.SUCCESS.equals(getOpenTimeResult.getStatusCode())) {
                    re.setStatusCode(ResultCode.ERROR);
                    re.setResultInfo("获取开奖时间异常,返回：" + getOpenTimeResult.getResultInfo().toString());
                    return re;
                }
                Date nextExpectOpenTime = (Date) getOpenTimeResult.getResultInfo();

                //期号还是当期，开奖时间为下期
                Map<String,Object> map = new HashMap<String, Object>();
                map.put("expect", expect);
                map.put("sleepTime", 0);
                map.put("openTime", nextExpectOpenTime);
                re.setStatusCode(ResultCode.SUCCESS);
                re.setResultInfo(map);
            }
        } catch (Exception e) {
        	  log.error(e.getMessage(), e);
            e.printStackTrace();
            re.setStatusCode(ResultCode.ERROR);
            re.setResultInfo("获取数据异常，" + e.getMessage());
        }
        return re;
    }

    /**
          * 获取下期开奖时间
     * @param betType 游戏类型
     * @param expect 期号
     * @return gameTime 游戏时间
     * @throws ParseException
     */
    public ResultEntity getNextExpectOpenDate(String betType, String expect, Integer gameTime)  {
        // testChangeURL();
        //返回数据
        ResultEntity re = new ResultEntity();
        re.setStatusCode(ResultCode.ERROR);

        List<BetResp> betList = getIssueBetList(betType, 3);
			  GameTypePlayingRelationTO relationTO = resourceClient.findByCode(betType);
        //尝试记录第一期
        if (!CollectionUtils.isEmpty(betList) && betList.get(0) != null) {
					GameApiLog apiLog = getApiLogByJson(betList.get(0));
					apiLog.setType(String.valueOf(relationTO.getGameTypePlayingTicket()));
					apiLog.setStatus("1");
					betService.insertApiLog(apiLog);
        }

        //如果最后一期都不能用，需要使用缓存开奖结果
        if (CollectionUtils.isEmpty(betList)) {
        	re.setResultInfo("无法获取结果，最新一期都不可用！");
        	re.setStatusCode(ResultCode.ERROR);
        	return re;
        }

        //过滤掉list中时间不满足的记录
        betList = betList.stream().filter(bet -> {
        	return checkJSONObject(bet, gameTime);
        }).collect(Collectors.toList());


        //开奖时间list
        List<Date> openTimeList = new ArrayList<>();
        for (BetResp bet : betList) {
        	Date thisOpenTime = getObjOpenTime(bet, expect, gameTime);
					if (thisOpenTime != null) {
							openTimeList.add(thisOpenTime);
					}
				}

        if (!CollectionUtils.isEmpty(openTimeList)) {
            long sumTimeNum = 0;
            for (Date openTime : openTimeList) {
                sumTimeNum += openTime.getTime();
            }

            long avgTimeNum = sumTimeNum / openTimeList.size();

            List<Date> tmpDateList = new ArrayList<>();
            tmpDateList.addAll(openTimeList);
            //晚于平均值的都去掉
            if (tmpDateList.size() >= 2) {
                for (int i = 0; i < tmpDateList.size(); i++) {
                    if((tmpDateList.get(i).getTime() - avgTimeNum) >= 0) {
                        openTimeList.remove(tmpDateList.get(i));
                    }
                }
                //再平均一次
                sumTimeNum = 0;
                for (int i = 0; i < openTimeList.size(); i++) {
                    sumTimeNum = sumTimeNum + openTimeList.get(i).getTime();
                }
                avgTimeNum = sumTimeNum / openTimeList.size();
            }
            Date openTime = new Date(avgTimeNum);
            re.setResultInfo(openTime);
            re.setStatusCode(ResultCode.SUCCESS);
        }
        return re;
    }

    private BetResp getIssueBet(String betType, Integer maxTryTimes) {
    	Map<String, String> queries = new HashMap<>();
    	queries.put("type", betType);
    	log.info("  ---getIssueBet. betType:  {}", betType);

			HttpResponse response = null;
			BetResp bet = null;

			HttpEntity entity = null;
			int tryTime = 0;
			try {
				GameTypePlayingRelationTO relationTO = resourceClient.findByCode(betType);
				GameApiService gameApiService = SpringApplicationUtils.getBean(GameApiService.class);
				String[] urls = gameApiService.findByTicket(relationTO.getGameTypePlayingTicket());
				while (bet == null && tryTime < maxTryTimes) {
					tryTime++;
					JSONArray array = GameResultGetUtils.get(urls);
					if (array != null) {
						List<BetResp> betResps = array.toJavaList(BetResp.class);
						if(!CollectionUtils.isEmpty(array)) {
							//最近一组数据
							bet = betResps.get(0);
						}
					} else {
						log.info("请求开奖尝试次数[{}], 请求开奖地址[{}], 未获得开奖结果数据, 休息500毫秒之后将继续进行", tryTime, relationTO.getGameLotteryApiUrl());
						TimeUnit.MILLISECONDS.sleep(500L);
					}
				}

			} catch (Exception e) {
				log.error(e.getMessage(), e);
			}
			return bet;
    }

    private List<BetResp> getIssueBetList(String betType, Integer maxTryTimes) {
			int tryTime = 0;
			try {
				GameTypePlayingRelationTO relationTO = resourceClient.findByCode(betType);
				GameApiService gameApiService = SpringApplicationUtils.getBean(GameApiService.class);
				String[] urls = gameApiService.findByTicket(relationTO.getGameTypePlayingTicket());
				while(tryTime < maxTryTimes) {
					tryTime++;
					JSONArray array = GameResultGetUtils.get(urls);
					if (array != null) {
						return array.toJavaList(BetResp.class);
					} else {
						log.info("请求开奖尝试次数[{}], 请求开奖地址[{}], 未获得开奖结果数据, 休息1000毫秒之后将继续进行", tryTime,  "http://120.79.209.59/ingapi.php?type=" + betType);
						TimeUnit.MILLISECONDS.sleep(1000L);
					}
				}
			} catch (Exception e) {
					log.error(e.getMessage(), e);
			}
			return null;
    }

    private GameApiLog getApiLogByJson(BetResp betResp){
        GameApiLog apiLog = new GameApiLog();
        apiLog.setId(UUIDUtils.randomUUID());
        apiLog.setIssueApi(betResp.getNumber());
        apiLog.setOpenTime(DateUtils.parseDate(betResp.getTime()));
        apiLog.setResult(JSON.toJSONString(betResp));
        return apiLog;
    }

    /**
     * 检查JSONOBject是否是有效数据
     * 比如19:00维护到20:00   20:05 获取到的前几期有19:00之前开的，需要把它们排除掉，   如果距离当前时间超出8个游戏单位时间，则认为是无效数据
     * @param bet 需要校验的数据
     * @param gameTime 游戏时间
     *   提前量，要校验的期，是当前的前一期 还是前两期 还是前三期
     * @throws ParseException
     */
    private boolean checkJSONObject(BetResp bet, int gameTime)  {
        try {
            Date openTime = bet.getTime();
            long timdif = ((new Date()).getTime() - openTime.getTime()) / 1000;
            if (timdif > gameTime * 8) {
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
          * 根据objTmp里的time时间计算 expect期的时间
          * 比如 objTmp是 001期 2018-08-19 15:30:00   获取expect为005期的数据时，为objTmp的时间加上 （5-1）*gameTime
     * @param bet
     * @return
     */
    private static Date getObjOpenTime(BetResp bet, String expect, int gameTime){
        Date openTime = bet.getTime();
        //获取期号差异
        String objExpect = bet.getNumber();
        int expectDiffInt = BetUtils.getExpectNum(expect) - BetUtils.getExpectNum(objExpect);
        openTime = BetUtils.addSeconds(openTime, expectDiffInt * gameTime);
        return openTime;
    }

    private final String prefix = "roomType_";

	/**
	 * 构建一个带前缀的key
	 *
	 * @param typeId
	 * @return
	 */
	public String buildKey(String typeId) {
		return prefix + typeId;
	}

	/**
	 * 构建一个带前缀的key
	 *
	 * @param currentKey
	 * @return
	 */
	public String getOriginalKey(String currentKey) {
		String key = currentKey.replace(prefix, "");
		return DataUtil.betTypeMap.get(key);
	}
}
